import { _decorator, Component, Node, Prefab, ScrollView, UITransform, Enum, MaskComponent, Vec3, Size, math, isValid, Widget } from "cc";
import { PoolManager } from "../pool_manager";
const { ccclass, menu, property, disallowMultiple, requireComponent, executeInEditMode } = _decorator;

export const SCROLL_NUM = Enum({
    /**
     * !#en Items arranged from top to bottom.
     * !#zh 从上到下排列
     * @property {Number} TOP_TO_BOTTOM
     */
    TOP_TO_BOTTOM: 0,
    /**
     * !#en Items arranged from left to right.
     * !#zh 从左往右排列
     * @property {Number} LEFT_TO_RIGHT
     */
    LEFT_TO_RIGHT: 1,
});

export const SCROLL_TYPE = Enum({
    /**
     * !#en Items arranged from top to bottom.
     * !#zh 从上到下滑动
     * @property {Number} 上下滑动
     */
    //'上下滑动' = SCROLL_NUM.TOP_TO_BOTTOM,
    //TOP_TO_BOTTOM: 0,
    '上下滑动': 0,
    /**
     * !#en Items arranged from left to right.
     * !#zh 从左往右滑动
     * @property {Number} 左右滑动
     */
    //'左右滑动' = SCROLL_NUM.LEFT_TO_RIGHT,
    //LEFT_TO_RIGHT: 1
    '左右滑动': 1
});

@ccclass("reuseList")
@menu('自定义组件/reuseList')
@executeInEditMode
@requireComponent(ScrollView)
@disallowMultiple
export class ReuseList extends Component {
    /**
     * 容器内左边距
     * @property {ReuseList.SCROLL_NUM} scrollType
     */
    @property({ tooltip: '选择滚动类型', displayName: '滚动类型', type: SCROLL_TYPE })
    public scrollType: any = SCROLL_NUM.TOP_TO_BOTTOM;
    /**
     * 容器内左边距
     * @property {Number} paddingLeft
     */
    @property({ tooltip: '容器内左边距' })
    public paddingLeft: number = 0;

    /**
     * 容器内上边距
     * @property {Number} paddingTop
     */
    @property({ tooltip: '容器内上边距' })
    public paddingTop: number = 0;

    /**
     * 子节点之间的水平间距。
     * @property {Number} spacingX
     */
    @property({ tooltip: '子节点之间的水平间距' })
    public spacingX: number = 0;

    /**
     * 子节点之间的垂直间距。
     * @property {Number} spacingY
     */
    @property({ tooltip: '子节点之间的垂直间距' })
    public spacingY: number = 0;

    /**
     * 子节点的Prefab。
     * @property {cc.Prefab} itemPrefab
     */
    @property({ tooltip: '子节点的Prefab', type: Prefab })
    public itemPrefab!: Prefab;

    /**
     * prefab缩放大小。
     * @property {cc.Prefab} prefabScale
     */
    @property({ tooltip: 'prefab缩放大小' })
    public prefabScale: number = 1;

    /**
     * 子节点大小可变
     * @property {boolean} variableSizeOfChild
     */
    @property({ tooltip: '子节点大小可变' })
    public variableSizeOfChild: boolean = false;

    public ndPanel!: Node;//界面节点
    public scriptPanel!: any;//界面脚本

    protected _arrBatchChildName: string[] = [];//子节点的子项合批（如背景，重复的文本）    
    protected _updateTimer: number = 0;
    protected _updateInterval: number = 0.016;//默认0.2
    protected _datas: any[] = [];
    protected _scrollView!: ScrollView;
    protected _ndContent!: Node;
    protected _isChange: boolean = false;
    protected _positions: Vec3[] = [];
    protected _count: number = 0;
    protected _mapElement: Map<string, Node> = new Map();//子项里面的子节点数据, 如bg-1,子节点
    protected _ndView!: Node;
    protected _mapElementPos: Map<string, Vec3> = new Map();//子项里面子节点的位置
    protected _isBatch: boolean = false;

    onLoad () {
        this._scrollView = this.node.getComponent(ScrollView) as ScrollView;
        this._ndContent = this._scrollView.content as Node;
        this._ndView = this.node.getChildByName("view") as Node;
        this._ndContent.getComponent(UITransform)?.setAnchorPoint(0, 1);
        let widgetCom = this._ndContent.getComponent(Widget);
        if (!widgetCom) {
            widgetCom = this._ndContent.addComponent(Widget);
        }

        widgetCom.top = 0;
        widgetCom.bottom = 0;
        widgetCom.isAlignLeft = true;
        widgetCom.isAlignTop = true;
        widgetCom.isAlignRight = false;
        widgetCom.isAlignBottom = false;
        widgetCom.isAlignVerticalCenter = false;
        widgetCom.isAlignHorizontalCenter = false;
        widgetCom.updateAlignment();

        if (this.scrollType === SCROLL_NUM.TOP_TO_BOTTOM) {
            this._scrollView.vertical = true;
            this._scrollView.horizontal = false;
        } else if (this.scrollType === SCROLL_NUM.LEFT_TO_RIGHT) {
            this._scrollView.vertical = false;
            this._scrollView.horizontal = true;
        }
    }

    onEnable () {
        if (!this.variableSizeOfChild) return;
        this._ndContent.on(Node.EventType.CHILD_ADDED, this._doLayout, this);
        this._ndContent.on(Node.EventType.CHILD_REMOVED, this._doLayout, this);
    }

    onDisable () {
        if (!this.variableSizeOfChild) return;
        this._ndContent.off(Node.EventType.CHILD_ADDED, this._doLayout, this);
        this._ndContent.off(Node.EventType.CHILD_REMOVED, this._doLayout, this);
    }

    /**
     * 初始化列表数据
     *
     * @param {any[]} datas 列表数据
     * @param {boolean} [isBatch=false] 是否合批/合并：讲item节点下的字节点抽出来放到新的父节点下，方便降低drawCall，默认合并item下所有的字节点
     * @param {string[]} [arrBatchChildName] 需要合并的item下的节点名字
     * @param {Node} [ndPanel] 界面节点
     * @param {any} [scriptPanel] 界面脚本
     * @memberof ReuseList
     */
    public init (datas: any[], isBatch: boolean = false, arrBatchChildName?: string[], ndPanel?: Node, scriptPanel?: any) {
        if (!isValid(this._ndContent)) {
            return
        }
        this.clear();
        this._datas = datas instanceof Array ? datas : [];
        this._isBatch = isBatch && datas.length > 0;
        this._arrBatchChildName = arrBatchChildName instanceof Array ? arrBatchChildName : [];
        this.ndPanel = ndPanel!;
        this.scriptPanel = scriptPanel!;

        this._isChange = true;
        this._positions = [];
        this._mapElement.clear();
        this._mapElementPos.clear();

        const size: Readonly<math.Size> = this.node.getComponentInChildren(MaskComponent)!.node.getComponent(UITransform)?.contentSize as Readonly<math.Size>;
        if (this.scrollType === SCROLL_NUM.TOP_TO_BOTTOM) {
            //几列
            this._count = Math.floor((size.width - this.paddingLeft) / (this._getPrefabWidth() + this.spacingX));
        } else {
            //几行
            this._count = Math.floor((size.height - this.paddingTop) / (this._getPrefabHeight() + this.spacingY));
        }

        this._count = this._count > 0 ? this._count : 1;

        let i = 0;
        for (; i < this._datas.length; i++) {
            let widthIndex;
            let heightIndex;
            if (this.scrollType === SCROLL_NUM.TOP_TO_BOTTOM) {
                widthIndex = i % this._count;
                heightIndex = Math.floor(i / this._count);
            } else {
                widthIndex = Math.floor(i / this._count);
                heightIndex = i % this._count;
            }

            const width = this._getPrefabWidth();
            const height = this._getPrefabHeight();

            const x = this.paddingLeft + this.spacingX * widthIndex + width * (widthIndex + 1 / 2);
            const y = -(this.paddingTop + this.spacingY * heightIndex + height * (heightIndex + 1 / 2));
            this._positions.push(new Vec3(x, y, 0));
        }

        // 设置节点大小
        const sizeWidth = this._getPrefabWidth();
        const sizeHeight = this._getPrefabHeight();

        let ui: UITransform = this._ndContent.getComponent(UITransform) as UITransform;
        if (this.scrollType === SCROLL_NUM.TOP_TO_BOTTOM) {
            const sizeHeightIndex = Math.ceil(i / this._count);
            ui.setContentSize(
                math.size(size.width, this.paddingTop + this.spacingY * sizeHeightIndex + sizeHeight * sizeHeightIndex),
            );
        } else {
            const sizeWidthIndex = Math.ceil(i / this._count);
            ui.setContentSize(
                math.size(this.paddingLeft + this.spacingX * sizeWidthIndex + sizeWidth * sizeWidthIndex, size.height),
            );
        }

        //创建可以合批的节点的父节点，放在view节点下
        if (this._isBatch) {
            for (let i = this._ndView.children.length - 1; i >= 0; i--) {
                const node = this._ndView.children[i];
                if (node.name !== this._ndContent.name && node.name !== "MASK_CHILD") {
                    node.removeFromParent();
                }
            }

            let arrChildName = [];
            this.itemPrefab.data.children.forEach(element => {
                arrChildName.push(element.name);
            });

            //如果没有指定该合并的子节点则默认分别合并同类的子节点
            if (!this._arrBatchChildName.length) {
                this._arrBatchChildName = [...arrChildName];
            } else {
                for (let i = this._arrBatchChildName.length; i >= 0; i--) {
                    const childName = this._arrBatchChildName[i];
                    if (arrChildName.indexOf(childName) === -1) {
                        this._arrBatchChildName.splice(i, 1);
                    }
                }
            }

            const uiComContent = this._ndContent.getComponent(UITransform) as UITransform;

            for (let i = 0; i < this._arrBatchChildName.length; i++) {
                const childName = this._arrBatchChildName[i];
                const ndElementParent = new Node();
                ndElementParent.parent = this._ndView;
                ndElementParent.name = childName;
                ndElementParent.addComponent(UITransform);
                const uiCom = ndElementParent.getComponent(UITransform) as UITransform;
                uiCom.setContentSize(uiComContent.contentSize);
            }

            while (this._ndContent.children.length) {
                this._ndContent.children[0].removeFromParent();
            }
        }
    }

    /**
     * 判断节点是否超出边界
     */
    protected _handleNode () {
        let child!: any;
        const num = [];
        for (let i = 0; i < this._datas.length; i++) {
            const viewPos = this._getPositionInView(this._positions[i]);
            if (!viewPos) {
                continue;
            }

            if (this._isOverBorder(viewPos)) {//超出边界-回收
                child = this._ndContent.getChildByName(String(i));
                if (child) {
                    // 超出边缘删除节点
                    this._remove(child, i);
                }
            } else {//不超出边界-展示卡片数据
                num.push(i);
                child = this._ndContent.getChildByName(String(i));
                if (!child) {
                    // 可视范围内显示节点ƒ
                    this._create(child, i);
                } else if (this._isChange) {
                    // this.node.emit('show', { index: i, node: child, data: this._datas[i] });
                    this._showCardItem(child, this.itemPrefab.data._name, [this._datas[i], i, this]);
                    child.setPosition(this._positions[i]);
                    child.name = String(i);
                }

                this._updateBatchChildPos(child, i, true);
            }
        }

        // 清除多余节点
        if (this._isChange) {
            const { children } = this._ndContent;
            for (let i = 0; i < children.length;) {
                child = children[i];
                if (num.indexOf(parseInt(child.name)) === -1) {
                    this._remove(child, child.name);
                } else {
                    i++;
                }
            }
        }

        this._isChange = false;
    }

    /**
     * 刷新合批节点下的子节点位置
     * @param ndChild 节点 
     * @param i 索引
     * @param isShow 是否展示
     */
    protected _updateBatchChildPos (ndChild: Node, i: number, isShow: boolean) {
        if (this._arrBatchChildName.length && this._mapElement.size && this._mapElementPos.size) {
            if (isShow) {
                this._arrBatchChildName.forEach((elementName: string) => {
                    const ndElement = this._mapElement.get(elementName + i);
                    if (isValid(ndElement)) {
                        const worldPos = this._ndContent.getComponent(UITransform).convertToWorldSpaceAR(this._positions[i]);
                        const originPos = this._mapElementPos.get(elementName)
                        const ndParent = this._ndView.getChildByName(elementName) as Node;
                        const localPos = ndParent.getComponent(UITransform).convertToNodeSpaceAR(worldPos);
                        localPos.add(originPos);
                        ndElement.setPosition(localPos);
                    }
                })
            } else {
                //添加到原来的节点和位置并回收掉
                this._arrBatchChildName.forEach((elementName: string) => {
                    const ndElement = this._mapElement.get(elementName + i);
                    if (isValid(ndElement)) {
                        ndElement.parent = ndChild;
                        const originPos = this._mapElementPos.get(elementName)
                        ndElement.setPosition(originPos);
                    }
                });
            }
        }
    }

    /**
     * 获取子节点高
     */
    protected _getPrefabHeight () {
        let ui: UITransform = this.itemPrefab.data.getComponent(UITransform) as UITransform;
        return ui.height * this.prefabScale;
    }

    /**
     * 获取子节点宽
     */
    protected _getPrefabWidth () {
        let ui: UITransform = this.itemPrefab.data.getComponent(UITransform) as UITransform;
        return ui.width * this.prefabScale;
    }

    /**
     * 获取子节点的位置
     */
    protected _getPositionInView (position: Vec3) { // get item position in scrollview's node space
        const worldPos = this._ndContent.getComponent(UITransform)!.convertToWorldSpaceAR(position);
        const viewPos = this.node.getComponent(UITransform)!.convertToNodeSpaceAR(worldPos);
        return viewPos;
    }

    onDestroy () {
        PoolManager.instance.clearPool(this.itemPrefab.data.name);
    }

    /**
     * 生成节点
     * @param {*} ndChild
     * @param {*} index
     */
    protected _create (ndChild: any, index: number) {
        ndChild = PoolManager.instance.getNode(this.itemPrefab, this._ndContent) as Node;
        if (this.variableSizeOfChild) ndChild.on(Node.EventType.SIZE_CHANGED, this._doLayout, this);
        ndChild.setScale(this.prefabScale, this.prefabScale, this.prefabScale);
        ndChild.setPosition(this._positions[index]);
        ndChild.name = String(index);
        // this.node.emit('show', { index: index, node: child, data: this._datas[index] });
        this._showCardItem(ndChild, this.itemPrefab.data._name, [this._datas[index], index, this]);

        //判断是否合批：将item的子节点放到新的父节点
        if (this._isBatch) {
            this._arrBatchChildName.forEach((childName: string) => {
                const ndElement = ndChild.getChildByName(childName) as Node;

                //初始化子项（背景，文字等）位置偏差
                if (this._mapElementPos.size < this._arrBatchChildName.length) {
                    this._mapElementPos.set(childName, ndElement.getPosition());
                }

                const ndParent = this._ndView.getChildByName(ndElement.name);
                ndElement.removeFromParent();
                ndParent.addChild(ndElement);
                this._mapElement.set(ndElement.name + index, ndElement);
            })
        }
    }

    /**
     * 移除子节点
     * @param {cc.Node} child
     */
    protected _remove (child: Node, index: number | string) {
        // if (this.variableSizeOfChild) child.off(Node.EventType.SIZE_CHANGED, this._doLayoutDirty, this);
        this._updateBatchChildPos(child, Number(index), false);
        child.name = this.itemPrefab.name;
        PoolManager.instance.putNode(child);
    }

    /**
     * 获取当前位置是否超出边界
     * @param {cc.v2} viewPos
     */
    protected _isOverBorder (viewPos: Vec3) {
        const size: Readonly<Size> = this.node.getComponentInChildren(MaskComponent)!.node.getComponent(UITransform)?.contentSize as Readonly<Size>;
        const { height } = size;
        const itemHeight = this._getPrefabHeight();
        const { width } = size;
        const itemWidth = this._getPrefabWidth();
        const borderHeight = height / 2 + itemHeight / 2;
        const borderWidth = width + itemWidth / 2;
        if (this.scrollType === SCROLL_NUM.TOP_TO_BOTTOM) {
            return viewPos.y > borderHeight || viewPos.y < -borderHeight;
        }
        return viewPos.x > borderWidth;
    }

    /**
     * this.datas数据改变时刷新所有子节点位置
     *
     * @private
     * @returns
     * @memberof ReuseList
     */
    protected _doLayout () {
        if (!this._datas.length) return;
        const size = this.node.getComponentInChildren(MaskComponent)!.node.getComponent(UITransform)!.contentSize;

        let i = 0;
        let curWidth = this.paddingLeft;
        let curHeight = this.paddingTop;
        let width;
        let height;
        this._positions = [];

        for (; i < this._datas.length; i++) {
            const child: Node = this._ndContent.getChildByName(String(i)) as Node;
            const UITransform: UITransform = this._ndContent.getChildByName(String(i))?.getComponent(UITransform) as UITransform;
            width = this._getPrefabWidth();
            height = this._getPrefabHeight();
            if (child) {
                width = UITransform.width;
                height = UITransform.height;
            }

            if (this.scrollType === SCROLL_NUM.TOP_TO_BOTTOM) {
                if (i % this._count === 0) {
                    curWidth = this.paddingLeft + width / 2;
                    if (i === 0) curHeight += height / 2;
                    else curHeight += height / 2 + this.spacingY;
                }
            } else if (i % this._count === 0) {
                curHeight = this.paddingTop + height / 2;
                if (i === 0) curWidth += width / 2;
                else curWidth += width / 2 + this.spacingY;
            }

            this._positions.push(new Vec3(curWidth, -curHeight, 0));
            if (child && this._positions[i] !== child.getPosition()) {
                child.setPosition(this._positions[i]);
            }
            if (this.scrollType === SCROLL_NUM.TOP_TO_BOTTOM) {
                curWidth += width + this.spacingX;
                if ((i + 1) % this._count === 0) curHeight += height / 2;
            } else {
                curHeight += height + this.spacingY;
                if ((i + 1) % this._count === 0) curWidth += width / 2;
            }
        }

        let ui: UITransform = this._ndContent.getComponent(UITransform) as UITransform;
        if (this.scrollType === SCROLL_NUM.TOP_TO_BOTTOM) {
            ui.setContentSize(size.width, curHeight);
        } else {
            ui.setContentSize(curWidth, size.height);
        }
    }

    /**
     * 展示卡片节点
     * @param ndChild 
     * @param scriptName 
     * @param args
     */
    protected _showCardItem (ndChild: Node, scriptName: any, args: any) {
        let script: any = ndChild.getComponent(scriptName);
        let script2: any = ndChild.getComponent(scriptName.charAt(0).toUpperCase() + scriptName.slice(1));

        if (script && script.init) {
            script.init.apply(script, args);
        } else if (script2 && script2.init) {
            script2.init.apply(script2, args);
        } else {
            throw `查找不到脚本文件${scriptName}`;
        }
    }

    /**
     * 替换item
     * @param item 新item
     * @param index 原来item的index
     */
    public replaceItem (item: any, index: number) {
        this._datas.pop();
        this._datas.splice(index, 0, item);
        this._isChange = true;
    }

    /**
     * 将某个item放到列表最后面
     * @param item 完成的item
     * @param index 完成的item的索引
     */
    public setItemToLast (item: any, index: number) {
        if (index !== this._datas.length - 1) {
            this._datas.splice(index, 1);

            for (let i = 0; i < this._ndContent.children.length; i++) {
                const ndChild = this._ndContent.children[i];
                if (i > index) {
                    ndChild.name = String(i - 1);
                } else if (i === index) {
                    ndChild.name = String(this._ndContent.children.length - 1);
                }
            }

            this._ndContent.children.sort((a: Node, b: Node) => {
                return Number(a.name) - Number(b.name);
            })

            this._datas.push(item);

            this._isChange = true;
        }
    }

    /**
     * 删除某个item
     * @param item 
     */
    public deleteItem (item: any) {
        let i = this._datas.indexOf(item);

        if (i !== -1) {
            this._datas.splice(i, 1);
            this._isChange = true;
        }
    }

    /**
     * 添加某个item
     * @param item 
     */
    public addItem (item: any) {
        this._datas.push(item);
        this._isChange = true;
    }

    public clear () {
        let child!: any;
        let count = this._ndContent.children.length;

        //注意不能用this._datas.length，因为其和参数data同时改变
        for (let i = 0; i < count; i++) {
            child = this._ndContent.getChildByName(String(i));
            if (child) {
                this._remove(child, i);
            }
        }

        this._datas = [];
        this._isChange = true;
    }

    update (dt: number) {
        this._updateTimer += dt;
        if (this._updateTimer < this._updateInterval) {
            return; // we don't need to do the math every frame
        }

        this._updateTimer = 0;
        this._handleNode();
    }
};

/**
 * 另一种派发用法
 * 子节点显示监听事件，通过 this.node.on('show', callback, this); 进行监听。
 * @event show
 * @param {Object} obj 返回节点信息对象
 *        {number} obj.index  节点索引
 *        {cc.Node} obj.node  节点
 *        {Object} obj.data  节点信息
 * @example
 * this.node.on('show', callback, this);
 * const callback = (obj) => {
 *     const index = detail.index; //  节点索引
 *     const node = detail.node; // 节点
 *     const data = detail.data; // 节点信息
 * }
 * this.node.off('show', callback, this);
 */